# -*- coding: utf-8 -*-
# @Time    : 2020/11/21 15:22
# @Author  : darknight
# @File    : main.py
# @Software: PyCharm
"""
QQ音乐爬取
"""
import json
import os
import time
import execjs

from core.spider import BaseSpider

BASE_DIR = os.path.dirname(__file__)


class Spider(BaseSpider):
    NAME = 'qq'

    # 分类歌单的对应ID
    # 华语(1)、欧美(2)、韩语(3)、日语(4)、流行(5)、纯音乐(5)、摇滚(7)、电子(8)、民谣(9)
    # 爬取分类: 原本分类
    SONGS_LIST_CATEGORY_ID = {
        2: 167,
        1: 165,
        3: 168,
        4: 169,
        5: 6,
        6: 15,
        7: 11,
        8: 24,
        9: 28,
    }

    # 排行榜对应的ID
    # 飙升(1)、新歌(2)、热歌(3)、原创(4)、抖音(5)
    TOP_LIST_ID = {
        1: 62,
        2: 27,
        3: 26,
        4: 4,
        5: 60,
    }

    def __init__(self):
        super(Spider, self).__init__()
        self.SIGN_URL = "https://u.y.qq.com/cgi-bin/musics.fcg"
        self.header_update({
            'origin': 'https://y.qq.com',
            'referer': 'https://y.qq.com/',
        })
        self._js = self._init_sign_js()

        self.params = {
            "format": "json",
            "new_json": "1",
        }

    @staticmethod
    def _init_sign_js():
        """初始化sign文件"""
        with open(BASE_DIR + '/js/sign.js', 'r') as f:
            return execjs.compile(f.read())

    def _get_sign(self, data):
        """
        获取 sign
        :param data: 加密参数
        :return:
        """
        return self._js.call('get_sign', data)

    def get_recommend_songs_list(self):
        """
        获取推荐歌单
        :return:
        """
        data = json.dumps({"comm": {"ct": 24},
                           "recomPlaylist": {"method": "get_hot_recommend", "param": {"async": 1, "cmd": 2},
                                             "module": "playlist.HotRecommendServer"}})
        self.params.update({
            "sign": self._get_sign(data),
            "data": data
        })
        try:
            res = self.session.get("https://u.y.qq.com/cgi-bin/musics.fcg", params=self.params).json()
            assert res['code'] == 0, f"获取推荐歌单。\n返回数据：{res}"
            return self.return_res(res)
        except KeyError as e:
            print("返回获取推荐歌单数据解析失败", e)

    def get_recommend_songs(self):
        """
        获取推荐歌曲
        :return:
        """
        data = json.dumps({"comm": {"ct": 24},
                           "new_song": {"module": "newsong.NewSongServer", "method": "get_new_song_info",
                                        "param": {"type": 1}}, })
        self.params.update({
            "sign": self._get_sign(data),
            "data": data
        })
        try:
            res = self.session.get("https://u.y.qq.com/cgi-bin/musics.fcg", params=self.params).json()
            assert res['code'] == 0, f"获取推荐歌曲。\n返回数据：{res}"
            return self.return_res(res)
        except KeyError as e:
            print("返回获取推荐歌曲数据解析失败", e)

    def get_songs_list_by_category(self, category_id, sort_id=1, offset=0, limit=20):
        """
        获取歌单列表
        配合 get_song_list_category 使用
        sort_id:  配合某一category_id 可能不会获得想要的数据
            推荐：5
            最新：2
            最热：3
            评分：4
            默认：1
        :param category_id:
        :param sort_id:
        :param offset:
        :param limit:
        :return:
        """
        self.params.update({
            'picmid': "1",
            'rnd': '0.781312443411776',
            'categoryId': category_id,
            'sortId': sort_id,
            'sin': f'{offset}',  # 起始值  offset
            'ein': f'{limit - 1}',  # 数量+1  limit   20条数据输入19即可
        })
        try:
            res = self.session.get("https://c.y.qq.com/splcloud/fcgi-bin/fcg_get_diss_by_tag.fcg",
                                   params=self.params).json()
            assert res['code'] == 0, f"获取分类失败。\n返回数据：{res}"
            return self.return_res(res)
        except KeyError as e:
            print("返回歌单列表数据解析失败", e)

    def get_songs_list_detail(self, list_id):
        """
        获取歌单详情
        :param song_list_id:  歌单ID
        :return:
        """
        self.params.update({
            "new_format": "1",
            "type": "1",
            "disstid": list_id})
        try:
            res = self.session.get("https://c.y.qq.com/qzone/fcg-bin/fcg_ucc_getcdinfo_byids_cp.fcg",
                                   params=self.params).json()
            assert res['code'] == 0, f"歌单详情获取失败。\n返回数据：{res}"

            return self.return_res(res)
        except KeyError as e:
            print("返回歌单详情数据解析是失败", e)

    def get_song_detail(self, song_mid):
        """
        获取音乐详情
        :param song_id: 音乐 id
        :return:
        """
        data = json.dumps({"comm": {"ct": 24, "cv": 0},
                           "songinfo": {"method": "get_song_detail_yqq",
                                        "param": {"song_type": 0,
                                                  "song_mid": song_mid},
                                        "module": "music.pf_song_detail_svr"}})
        self.params.update({
            "sign": self._get_sign(data),
            "data": data
        })
        try:
            res = self.session.get("https://u.y.qq.com/cgi-bin/musics.fcg", params=self.params).json()
            assert res['code'] == 0, f"获取歌曲详情失败。\n返回数据：{res}"
            return self.return_res(res)

        except KeyError as e:
            print('返回歌曲详情解析错误', e)

    def get_song_lyric(self, song_id):
        """
        获取音乐歌词
        :param song_id: 音乐 id
        :return:
        """
        self.params.update({
            "nobase64": "1",
            "musicid": song_id,
        })
        try:
            res = self.session.get("https://c.y.qq.com/lyric/fcgi-bin/fcg_query_lyric_yqq.fcg",
                                   params=self.params).json()
            assert res['code'] == 0, f"获取歌词失败。\n返回数据：{res}"
            return self.return_res(res)

        except KeyError as e:
            print('返回歌曲详情解析错误', e)

    def get_song_play_url(self, song_mid):
        """
        获取音乐播放地址
        :param song_mid: 音乐 mid
        :return: 音乐播放地址
        """
        data = json.dumps({"req": {"module": "CDN.SrfCdnDispatchServer", "method": "GetCdnDispatch",
                                   "param": {"guid": "5491945423", "calltype": 0, "userip": ""}},
                           "req_0": {"module": "vkey.GetVkeyServer", "method": "CgiGetVkey",
                                     "param": {"guid": "5491945423", "songmid": [song_mid], "songtype": [0], "uin": "0",
                                               "loginflag": 1, "platform": "20"}},
                           "comm": {"uin": 0, "format": "json", "ct": 24, "cv": 0}})
        self.params.update({
            "sign": self._get_sign(data),
            "data": data
        })
        try:
            res = self.session.get("https://u.y.qq.com/cgi-bin/musics.fcg", params=self.params).json()
            assert res['code'] == 0, f"获取播放地址失败。\n返回数据：{res}"
            return self.return_res(res)
        except KeyError as e:
            print('返回音乐播放数据解析错误', e)

    def get_mv_play_url(self, vid):
        """
        获取MV详情
        :param vid:
        :return:
        """
        assert isinstance(vid, (list, str)), "请传入一个字符串或者列表"

        if type(vid) == str:
            vid = [vid]
        data = json.dumps({"getMvUrl": {"module": "gosrf.Stream.MvUrlProxy", "method": "GetMvUrls",
                                        "param": {"vids": vid, "request_typet": 10001, "addrtype": 3}}})
        try:
            res = self.session.post("https://u.y.qq.com/cgi-bin/musicu.fcg", data=data).json()
            assert res['code'] == 0, f"获取MV详情失败。\n返回数据：{res}"
            return self.return_res(res)
        except KeyError as e:
            print("返回榜单MV列表数据解析失败", e)

    def get_top_list(self, top_id, offset=0, limit=10):
        """
        获取榜单歌曲
            飙升： 62     time.strftime('%Y-%m-%d')
            新歌： 27
            流行： 4
            识曲： 67

            热歌：  26    time.strftime('%Y_%W')
            网络：  28
            舞曲：  63
            快手：  74
            抖音：  60
            综艺：  64
            影视：  29
            国风：  65
            说唱：  58
            电音：  57
            动漫：  72
            游戏：  73
            达人：  70
            K歌：   36
            YouTu： 128
        :param top_id:
        :param offset:
        :param limit:
        :return:
        """
        period = time.strftime('%Y-%m-%d' if int(top_id) in [62, 27, 4, 67] else '%Y_%W',
                               time.localtime(time.time() - 60 * 60 * 24))  # -1天，确保已更新
        data = json.dumps({"detail": {"module": "musicToplist.ToplistInfoServer", "method": "GetDetail",
                                      "param": {"offset": offset, "num": limit, "topId": top_id, "period": period}},
                           "comm": {"ct": 24, "cv": 0}})
        self.params.update({
            "sign": self._get_sign(data),
            "data": data,

        })
        try:
            res = self.session.get(self.SIGN_URL, params=self.params).json()
            assert res['code'] == 0, f"榜单获取失败。\n返回数据：{res}"
            return self.return_res(res)

        except KeyError as e:
            print('返回榜单数据解析错误', e)

    def get_search_song(self, kw):
        """
        搜索音乐
            t：0 歌曲
        :param kw: 关键词
        :return:
        """
        self.params.update({
            "nobase64": "1",
            "p": "1",
            "n": "10",
            "w": kw,
        })
        try:
            res = self.session.get("https://c.y.qq.com/soso/fcgi-bin/client_search_cp",
                                   params=self.params).json()
            assert res['code'] == 0, f"搜索{kw}失败。\n返回数据：{res}"
            return self.return_res(res)

        except KeyError as e:
            print('返回搜索数据解析错误', e)


if __name__ == '__main__':
    _song_mid = '000QJ4M13JBut8'
    _song_id = '284614725'
    _song_list_id = '2931035154'
    _category_id = 10000000
    _sort_id = 5
    _kw = '夜曲'
    _top_id = 62
    _regional = 1
    _album_mid = "004AAx9y4Tf5fc"
    _vid = "f0033cch1ur"
    # _vid = ["f0033cch1ur", 'w0035f1bcw0']

    qq = Spider()

    # **********  测试
    print(qq.get_recommend_songs_list())
    # print(qq.get_recommend_songs())
    # print(qq.get_songs_list_by_category(165))
    # print(qq.get_song_list_detail(_song_list_id))
    # print(qq.get_song_detail(_song_mid))
    # print(qq.get_song_lyric(_song_id))
    # print(qq.get_song_play_url(_song_mid))
    # print(qq.get_mv_play_url(_vid))
    # print(qq.get_top_list(_top_id))
    # print(qq.get_search_song('夜曲'))
